/**
 * serial lib async and isr
 */
#include <string.h>
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/usb/usb_device.h>
#include <zephyr/usb/usbd.h>

#include "serial.h"

#define LOG_MODULE_NAME serial
LOG_MODULE_REGISTER(LOG_MODULE_NAME, LOG_LEVEL_INF);

#define MAX_UART_SEND_SIZE 255
#define MAX_UART_RECV_SIZE 255

static struct k_thread rx_thread_data;
static K_THREAD_STACK_DEFINE(rx_thread_stack, 1024);

static struct serial_inst m_inst = {0};

static int serial_tx_internal(const uint8_t *data, uint16_t len);

#if defined(CONFIG_SERIAL_LIB_MODE_INTERRUPT)
static void uart_isr(const struct device *dev, void *user_data)
{
    ARG_UNUSED(user_data);

    if (uart_irq_update(dev) && uart_irq_is_pending(dev))
    {
        if (uart_irq_rx_ready(dev))
        {
            uint8_t buffer[MAX_UART_RECV_SIZE];
            int recv_len;
            size_t len = MIN(ring_buf_space_get(&m_inst.rx_ring_buf), sizeof(buffer));

            if (len == 0)
            {
                /* Throttle because ring buffer is full */
                uart_irq_rx_disable(dev);
                return;
            }

            recv_len = uart_fifo_read(dev, buffer, len);
            if (recv_len < 0)
            {
                LOG_ERR("Failed to read UART FIFO");
                return;
            }

            int rb_len = ring_buf_put(&m_inst.rx_ring_buf, buffer, recv_len);
            if (rb_len < recv_len)
            {
                LOG_ERR("Drop %u bytes", recv_len - rb_len);
            }

            LOG_DBG("tty fifo -> ringbuf %d bytes", rb_len);
            k_sem_give(&m_inst.rx_sem);
        }

        if (uart_irq_tx_ready(dev))
        {
            uint8_t buffer[MAX_UART_SEND_SIZE];
            int rb_len = ring_buf_get(&m_inst.tx_ring_buf, buffer, MAX_UART_SEND_SIZE);
            if (!rb_len)
            {
                // LOG_DBG("Ring buffer empty, disable TX IRQ");
                uart_irq_tx_disable(dev);
                m_inst.tx_busy = false;
                return;
            }

            int send_len = uart_fifo_fill(dev, buffer, rb_len);
            if (send_len < rb_len)
            {
                LOG_ERR("Drop %d bytes", rb_len - send_len);
            }

            // LOG_DBG("ringbuf -> tty fifo %d bytes", send_len);
        }
    }
}
#endif

static void uart_start_tx(const struct device *dev)
{
    if (!m_inst.tx_busy)
    {
        m_inst.tx_busy = true;
#if defined(CONFIG_SERIAL_LIB_MODE_INTERRUPT)
        uart_irq_tx_enable(dev);
#endif
    }
}

static int serial_tx_internal(const uint8_t *data, uint16_t len)
{
    size_t remaining_len = len;
    size_t offset = 0;

    while (remaining_len > 0)
    {
        size_t chunk_len = MIN(remaining_len, ring_buf_space_get(&m_inst.tx_ring_buf));
        if (chunk_len == 0)
        {
            /* Yield to let the ISR drain the TX ring buffer */
            k_yield();
            continue;
        }

        ring_buf_put(&m_inst.tx_ring_buf, &data[offset], chunk_len);
        remaining_len -= chunk_len;
        offset += chunk_len;
    }

    uart_start_tx(m_inst.uart_dev);
    return len;
}

#if defined(CONFIG_SERIAL_LIB_MODE_USB)
static inline void print_baudrate(const struct device *dev)
{
    uint32_t baudrate;
    int ret;

    ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
    if (ret)
    {
        LOG_WRN("Failed to get baudrate, ret code %d", ret);
    }
    else
    {
        LOG_INF("Baudrate %u", baudrate);
    }
}
#endif

static void rx_thread(void *unused1, void *unused2, void *unused3)
{
    ARG_UNUSED(unused1);
    ARG_UNUSED(unused2);
    ARG_UNUSED(unused3);

    while (1)
    {
        k_sem_take(&m_inst.rx_sem, K_FOREVER);
        uint8_t buffer[MAX_UART_RECV_SIZE];
        int rb_len = ring_buf_get(&m_inst.rx_ring_buf, buffer, sizeof(buffer));
        if (rb_len > 0)
        {
            LOG_HEXDUMP_DBG(buffer, rb_len, "rx_thread");

            if (m_inst.rx_callback)
            {
                m_inst.rx_callback(buffer, rb_len);
            }
        }
    }
}

struct serial_inst *serial_init(const struct device *uart_dev, rx_process_t rx_process_func)
{
    LOG_INF("Serial lib version: %s", CONFIG_SERIAL_LIB_VERSION);
    m_inst.uart_dev = uart_dev;
    if (!device_is_ready(m_inst.uart_dev))
    {
        LOG_ERR("CDC ACM device not ready");
        return NULL;
    }

    ring_buf_init(&m_inst.tx_ring_buf, sizeof(m_inst.tx_ring_buffer), m_inst.tx_ring_buffer);
    ring_buf_init(&m_inst.rx_ring_buf, sizeof(m_inst.rx_ring_buffer), m_inst.rx_ring_buffer);
    k_sem_init(&m_inst.rx_sem, 0, 1);
    m_inst.tx_busy = false;
    m_inst.tx = serial_tx_internal;
    m_inst.rx_callback = rx_process_func;

#if defined(CONFIG_SERIAL_LIB_MODE_USB)
    int ret;
    ret = usb_enable(NULL);
    if (ret != 0)
    {
        LOG_ERR("Failed to enable USB");
        return NULL;
    }

    LOG_INF("Wait for DTR");
    while (true)
    {
        uint32_t dtr = 0U;

        uart_line_ctrl_get(m_inst.uart_dev, UART_LINE_CTRL_DTR, &dtr);
        if (dtr)
        {
            break;
        }
        else
        {
            /* Give CPU resources to low priority threads. */
            k_sleep(K_MSEC(100));
        }
    }
    LOG_INF("DTR set");

    /* They are optional, we use them to test the interrupt endpoint */
    ret = uart_line_ctrl_set(m_inst.uart_dev, UART_LINE_CTRL_DCD, 1);
    if (ret)
    {
        LOG_WRN("Failed to set DCD, ret code %d", ret);
    }

    ret = uart_line_ctrl_set(m_inst.uart_dev, UART_LINE_CTRL_DSR, 1);
    if (ret)
    {
        LOG_WRN("Failed to set DSR, ret code %d", ret);
    }

    /* Wait 100ms for the host to do all settings */
    k_msleep(100);
    print_baudrate(m_inst.uart_dev);
#endif

    k_thread_create(&rx_thread_data, rx_thread_stack, K_THREAD_STACK_SIZEOF(rx_thread_stack),
                    rx_thread, NULL, NULL, NULL, 5, 0, K_NO_WAIT);

#if defined(CONFIG_SERIAL_LIB_MODE_INTERRUPT)
    uart_irq_callback_user_data_set(m_inst.uart_dev, uart_isr, &m_inst);
    uart_irq_rx_enable(m_inst.uart_dev);
#endif

    return &m_inst;
}

#if 0 // how to use
#include "serial.h"

// const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
static const struct device *uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart0));

static struct k_thread tx_thread_data;
static K_THREAD_STACK_DEFINE(tx_thread_stack, 1024);

struct serial_inst *serial_inst = NULL;

static void serial_cb(const uint8_t *const data, uint16_t len)
{
	LOG_HEXDUMP_DBG(data, len, "RX");
}

int main(void)
{
	serial_inst = serial_init(uart_dev, (rx_process_t)serial_cb);
	if (!serial_inst)
	{
		LOG_ERR("Failed to init serial lib.");
		return -1;
	}

	LOG_INF("Serial lib example start.");

	// Start TX thread to produce tons of data to send
	k_thread_create(&tx_thread_data, tx_thread_stack, K_THREAD_STACK_SIZEOF(tx_thread_stack),
					tx_thread, NULL, NULL, NULL, 7, 0, K_NO_WAIT);

	return 0;
}
#endif
